/*
 * THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND 
 * (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
 * CONSEQUENTLY, SEMTECH SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
 * CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
 * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
 * CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
 * 
 * Copyright (C) SEMTECH S.A.
 */
/*! 
 * \file       main.c
 * \brief              
 * \version    1.0
 * \date       
 * \author     
 */
/* -------------------------------------------------------------------------- */
/* --- DEPENDANCIES --------------------------------------------------------- */

/* fix an issue between POSIX and C99 */
#if __STDC_VERSION__ >= 199901L
    #define _XOPEN_SOURCE 600
#else
    #define _XOPEN_SOURCE 500
#endif
 
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>         /* memset */
#include <signal.h>         /* sigaction */
#include <time.h>           /* time, clock_gettime, strftime, gmtime */
#include <sys/time.h>       /* timeval */
#include <unistd.h>         /* getopt, access */
#include <stdlib.h>         /* atoi, exit */
#include <errno.h>          /* error messages */
#include <math.h>           /* modf */
#include <assert.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <getopt.h>     /* getopt_long */
#include <sys/stat.h>
#include <dlfcn.h>

#include "radio.h"
#include "loragw_hal.h"
#include "loragw_reg.h"
#include "transfer.h"
#include "gpio.h"

#include "radios_hal.h"


#define DEBUG_ARRAY(a,b,c)            for(a=0;a<b;++a) fprintf(stderr,"%x.",c[a]);fprintf(stderr,"end\n")
#define ARRAY_SIZE(a)   (sizeof(a) / sizeof((a)[0]))
#define STRINGIFY(x)    #x
#define STR(x)          STRINGIFY(x)

#define MSG(args...) printf(args) /* message that is destined to the user */
#define MSG_DEBUG(FLAG, fmt, ...)                                                                         \
            do  {                                                                                         \
                if (FLAG)                                                                                 \
                    fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
            } while (0)





static char *lib_dll_md134x = "/usr/local/lib/libmd134x.so";
static char *lib_dll_sx1301 = "/usr/local/lib/libsx1301.so";
static char *lib_dll_sx1276 = "/usr/local/lib/libsx1276.so";

static void *gs_134x_handle = NULL;
static void *gs_133x_handle = NULL;
/* -------------------------------------------------------------------------- */
/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */

//static void *pd_open_dll(char *dll);
//static void * pd_load_dll_function(void *handle, char *fname);
//static void pd_close_dll(void *handle);


void pd_open_dll(bool md134x)
{
    //void *handle;
    char *error;
    if(md134x){
        gs_134x_handle = dlopen(lib_dll_md134x, RTLD_NOW);
        if (gs_134x_handle == NULL) {
            printf("Open library %s error:%s\n", lib_dll_md134x, dlerror());
            exit(EXIT_FAILURE);
        }        
    }else{
        gs_133x_handle = dlopen(lib_dll_sx1301, RTLD_NOW);
        if (gs_133x_handle == NULL) {
            printf("Open library %s error:%s\n", lib_dll_sx1301, dlerror());
            exit(EXIT_FAILURE);
        }
    }
    return ;//handle;
}

void * pd_load_dll_function(bool md134x, char *fname)
{
    if(md134x){
        if (gs_134x_handle == NULL) {
            printf("Load dll library function error:%s\n", dlerror());
            return NULL;
        }
        return dlsym(gs_134x_handle, fname);
    }else{
        if (gs_133x_handle == NULL) {
            printf("Load dll library function error:%s\n", dlerror());
            return NULL;
        }
        return dlsym(gs_133x_handle, fname);
    }  
}

void pd_close_dll(bool md134x)
{
    if(md134x){
        if (gs_134x_handle == NULL) {
            printf("Close dll library.\n");
            return ;
        }
        dlclose(gs_134x_handle);
        gs_134x_handle = NULL;
    }else{
        if (gs_133x_handle == NULL) {
            printf("Close dll library.\n");
            return ;
        }
        dlclose(gs_133x_handle);
        gs_133x_handle = NULL;
    }
}



/**
@brief Configure the gateway board
@param conf structure containing the configuration parameters
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_board_setconf(bool md134x, struct lgw_conf_board_s conf)
{
    int ret = 0;
    int (*handler)(struct lgw_conf_board_s) = NULL;

    handler = pd_load_dll_function(md134x, "lgw_board_setconf");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_board_setconf in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(conf);
    
    return ret;     
}

/**
@brief Configure an RF chain (must configure before start)
@param rf_chain number of the RF chain to configure [0, LGW_RF_CHAIN_NB - 1]
@param conf structure containing the configuration parameters
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_rxrf_setconf(bool md134x, uint8_t rf_chain, struct lgw_conf_rxrf_s conf)
{
    int ret = 0;
    int (*handler)(uint8_t rf_chain, struct lgw_conf_rxrf_s) = NULL;

    handler = pd_load_dll_function(md134x, "lgw_rxrf_setconf");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_rxrf_setconf in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(rf_chain, conf);
    
    return ret;         
}

/**
@brief Configure an IF chain + modem (must configure before start)
@param if_chain number of the IF chain + modem to configure [0, LGW_IF_CHAIN_NB - 1]
@param conf structure containing the configuration parameters
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_rxif_setconf(bool md134x, uint8_t if_chain, struct lgw_conf_rxif_s conf)
{
    int ret = 0;
    int (*handler)(uint8_t if_chain, struct lgw_conf_rxif_s) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_rxif_setconf");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_rxif_setconf in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(if_chain, conf);
    
    return ret;      
    
}
/**
@brief Configure the Tx gain LUT
@param pointer to structure defining the LUT
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_txgain_setconf(bool md134x, struct lgw_tx_gain_lut_s *conf)
{
    int ret = 0;
    int (*handler)(struct lgw_tx_gain_lut_s *) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_txgain_setconf");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_txgain_setconf in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(conf);
    
    return ret;    
}

/**
@brief Connect to the LoRa concentrator, reset it and configure it according to previously set parameters
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_start(bool md134x)
{
    int ret = 0;
    int (*handler)(void) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_start");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_start in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler();
    
    return ret;
}

/**
@brief Stop the LoRa concentrator and disconnect it
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_stop(bool md134x)
{
    int ret = 0;
    int (*handler)(void) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_stop");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_stop in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler();
    
    return ret;    
}

/**
@brief A non-blocking function that will fetch up to 'max_pkt' packets from the LoRa concentrator FIFO and data buffer
@param max_pkt maximum number of packet that must be retrieved (equal to the size of the array of struct)
@param pkt_data pointer to an array of struct that will receive the packet metadata and payload pointers
@return LGW_HAL_ERROR id the operation failed, else the number of packets retrieved
*/
int pd_lgw_receive(bool md134x, uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data)
{
    int ret = 0;
    int (*handler)(uint8_t, struct lgw_pkt_rx_s *) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_receive");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_receive in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(max_pkt, pkt_data);
    
    return ret;    
    
}

/**
@brief Schedule a packet to be send immediately or after a delay depending on tx_mode
@param pkt_data structure containing the data and metadata for the packet to send
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else

/!\ When sending a packet, there is a 1.5 ms delay for the analog circuitry to start and be stable (TX_START_DELAY).
In 'timestamp' mode, this is transparent: the modem is started 1.5ms before the user-set timestamp value is reached, the preamble of the packet start right when the internal timestamp counter reach target value.
In 'immediate' mode, the packet is emitted as soon as possible: transferring the packet (and its parameters) from the host to the concentrator takes some time, then there is the TX_START_DELAY, then the packet is emitted.
In 'triggered' mode (aka PPS/GPS mode), the packet, typically a beacon, is emitted 1.5ms after a rising edge of the trigger signal. Because there is no way to anticipate the triggering event and start the analog circuitry beforehand, that delay must be taken into account in the protocol.
*/
int pd_lgw_send(bool md134x, struct lgw_pkt_tx_s pkt_data)
{
    int ret = 0;
    int (*handler)(struct lgw_pkt_tx_s) = NULL;

    //fprintf(stderr, "INFO: pd_load_dll_function: lgw_send.\n");
    handler = (int(*)(struct lgw_pkt_tx_s))pd_load_dll_function(md134x, "lgw_send");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_send in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    //fprintf(stderr, "INFO: EXEC lgw_send in dll.\n");
    ret = handler(pkt_data);
    
    return ret;     
}

/**
@brief Give the the status of different part of the LoRa concentrator
@param select is used to select what status we want to know
@param code is used to return the status code
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_status(bool md134x, uint8_t select, uint8_t *code)
{
    int ret = 0;
    int (*handler)(uint8_t, uint8_t *) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_status");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_status in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(select, code);
    
    return ret;         
}

/**
@brief Abort a currently scheduled or ongoing TX
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_abort_tx(bool md134x)
{
    int ret = 0;
    int (*handler)(void) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_abort_tx");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_abort_tx in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler();
    
    return ret;    
}

/**
@brief Return value of internal counter when latest event (eg GPS pulse) was captured
@param trig_cnt_us pointer to receive timestamp value
@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
*/
int pd_lgw_get_trigcnt(bool md134x, uint32_t* trig_cnt_us)
{
    int ret = 0;
    int (*handler)(uint32_t*) = NULL;

    
    handler = pd_load_dll_function(md134x, "lgw_get_trigcnt");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_get_trigcnt in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler(trig_cnt_us);
    
    return ret;    
}

/**
@brief Allow user to check the version/options of the library once compiled
@return pointer on a human-readable null terminated string
*/
const char* pd_lgw_version_info(bool md134x)
{
    char* ret ;
    char* (*handler)(void) = NULL;
    
    handler = pd_load_dll_function(md134x, "lgw_version_info");
    if(handler == NULL){
        fprintf(stderr, "ERROR: Failed to get lgw_version_info in dll.\n");
        pd_close_dll(md134x);
        exit(EXIT_FAILURE);
    }
    ret = handler();
    
    return ret;        
}



uint32_t pd_get_time_on_air(bool md134x, struct lgw_pkt_tx_s *pkt)
{
    uint32_t time = 0;
    uint32_t (*time_on_air_full)(void *) = NULL;
    uint32_t (*time_on_air_half)(void *, bool) = NULL;
    
    if(md134x){
        time_on_air_full = pd_load_dll_function(md134x, "lgw_time_on_air");
        if(time_on_air_full == NULL){
            fprintf(stderr, "ERROR: Failed to get lgw_time_on_air in dll.\n");
            pd_close_dll(md134x);
            exit(EXIT_FAILURE);
        }
        time = time_on_air_full(pkt);
        
        return time;
    }else{
        time_on_air_half = pd_load_dll_function(md134x, "lgw_time_on_air");
        if(time_on_air_half == NULL){
            fprintf(stderr, "ERROR: Failed to get lgw_time_on_air in dll.\n");
            pd_close_dll(md134x);
            exit(EXIT_FAILURE);
        }
        time = time_on_air_half(pkt, false);
        
        return time;    
    }    
}










